﻿using PluginNET.error;
using PluginNET.events;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;

namespace PluginNET
{
    /// <summary>
    /// 插件管理器
    /// </summary>
    /// <typeparam name="T">不同接口的枚举名称</typeparam>
    public sealed class PluginManager<T> : IDisposable where t: struct
    {
        #region 初始化
        /// <summary>
        /// 存储插件的目录
        /// </summary>
        private readonly string pluginFolder;

        private readonly string prefix;
        private readonly Assembly asm;

        private IEnumerable<MethodInfo> InterfacesDefination;

        private Dictionary<MethodInfo> InterfacesDefination;
        /// <summary>
        ///       初始化插件管理器
        /// </summary>
        /// <param name="interfacesDefination"></param>
        /// <param name="prefix">插件入口类名的前缀</param>
        /// <param name="pluginFolder">插件所在目录，绝对路径或相对路径</param>
        public PluginManager(IEnumerable<MethodInfo> interfacesDefination, string prefix, string pluginFolder = "plugin")
        {
            asm = Assembly.GetExecutingAssembly();
            this.prefix = prefix;
            InterfacesDefination = interfacesDefination;
            this.pluginFolder = Path.IsPathRooted(pluginFolder)
             ? pluginFolder : Path.Combine(asm.Location, pluginFolder);

            // 如果插件目录不存在，就创建
            if (!Directory.Exists(this.pluginFolder))
            {
                Directory.CreateDirectory(this.pluginFolder);
            }
        }

        #endregion

        #region 事件

        /// <summary>
        /// 程序集加载前的事件
        /// </summary>
        public event PluginEvent OnAssemblyLoading;

        /// <summary>
        /// 程序集加载后的事件
        /// </summary>
        public event PluginEvent OnAssemblyLoaded;

        /// <summary>
        /// 插件类加载后的事件
        /// </summary>
        public event PluginEvent OnPluginLoaded;

        /// <summary>
        /// 加载插件失败的事件
        /// 错误类型参见 <see cref="PluginErrorTypes"/>
        /// </summary>
        public event PluginErrorEvent OnError;

        #endregion

        /// <summary>
        /// 加载插件目录下的所有插件
        /// </summary>
        /// <param name="filter">插件过滤器，返回false表示不加载这个插件</param>
        public void Load(Func<string, bool> filter = null)
        {
            // 遍历插件目录下的dll
            foreach (var filename in Directory.GetFiles(pluginFolder, "*.dll") // 筛选dll文件
                .Where(filename => filter == null || filter(filename)) // 如果调用Load时有过滤器，则调用过滤器                
                )
            {
                LoadPlugin(filename);
            }
        }

        /// <summary>
        /// 加载插件
        /// </summary>
        /// <param name="filename"></param>
        private void LoadPlugin(string filename)
        {
            var assembly = LoadAssembly(filename);
            if (assembly == null)
            {
                return;
            }
            LoadPluginType(filename, assembly);
        }

        /// <summary>
        /// 加载程序集
        /// </summary>
        /// <param name="filename"></param>
        /// <returns></returns>
        private Assembly LoadAssembly(string filename)
        {
            // 加载程序集
            try
            {
                // 加载程序集前发出一个事件，如果程序不想用这个文件呢？对吧
                if (EmitPluginEvent(OnAssemblyLoading, new PluginEventArgs()
                {
                    FileName = filename
                }))
                {
                    return null;
                }

                // 加载
                var assembly = Assembly.LoadFrom(filename);

                // 加载后再发出一个事件，如果程序现在才知道不想用这个文件呢？对吧
                if (EmitPluginEvent(OnAssemblyLoaded, new PluginEventArgs()
                {
                    FileName = filename,
                    Assembly = assembly
                }))
                {
                    return null;
                }

                return assembly;
            }
            catch (Exception ex)
            {
                // 啊哦  程序集加载失败了
                EmitPluginErrorEvent(new PluginErrorEventArgs()
                {
                    FileName = filename,
                    Exception = ex,
                    ErrorType = PluginErrorTypes.InvalidManagedDllFile
                });
                return null;
            }
        }

        /// <summary>
        /// 从程序集加载插件的实现类（仅加载第一个）
        /// </summary>
        /// <param name="filename">这里搞个文件名，只为了在发出事件时可以加上文件名</param>
        /// <param name="assembly">程序集对象</param>
        private void LoadPluginType(string filename, Assembly assembly)
        {
            Type clazz;
            try
            {
                // 查找序集内的有效类型
                clazz = assembly.GetTypes().FirstOrDefault(type =>
                    type.IsClass &&
                    type.IsVisible &&
                    type.Name.StartsWith(prefix)
                    );
            }
            catch (Exception ex)
            {
                // 啊哦  类型加载失败了  这里搞个事件，为了让程序知道有个插件加载失败了
                EmitPluginErrorEvent(new PluginErrorEventArgs()
                {
                    FileName = filename,
                    Assembly = assembly,
                    Exception = ex,
                    ErrorType = PluginErrorTypes.CannotLoadClassTypes
                });
                return;
            }

            // 插件内没有找到继承接口的类  可能不是插件
            if (clazz == null)
            {
                EmitPluginErrorEvent(new PluginErrorEventArgs()
                {
                    FileName = filename,
                    Assembly = assembly,
                    ErrorType = PluginErrorTypes.PluginClassNotFound
                });
                return;
            }


            try
            {
                // 创建实例前，发出事件，可以阻止一下
                EmitPluginEvent(OnInstanceCreating, new PluginLoadArgs()
                {
                    Assembly = assembly,
                    ClassType = clazz
                });
                // 创建实例
                var instance = assembly.CreateInstance(clazz.FullName);
                if (instance == null)
                {
                    // 创建实例没有报错，但实例为null，发出事件
                    EmitPluginErrorEvent(new PluginErrorEventArgs()
                    {
                        FileName = filename,
                        Assembly = assembly,
                        ClassType = clazz,
                        ErrorType = PluginErrorTypes.ImplementionClassNotFound
                    });
                    return;
                }

                // 创建实例成功后，发出事件
                EmitPluginEvent(OnInstanceCreated, new PluginInstanceCreatedArgs<InterfaceClass>()
                {
                    Assembly = assembly,
                    ClassType = clazz,
                    Instance = instance as InterfaceClass
                });
            }
            catch (Exception ex)
            {
                // 创建实例时发生了异常，发出事件
                EmitPluginErrorEvent(new PluginErrorEventArgs()
                {
                    FileName = filename,
                    Assembly = assembly,
                    ClassType = clazz,
                    ErrorType = PluginErrorTypes.InstanceCreateFailed,
                    Exception = ex
                });
            }
        }

        /// <summary>
        /// 发出插件事件的公共函数
        /// </summary>
        /// <returns>返回true阻止插件继续加载</returns>
        private bool EmitPluginEvent<ArgType>(PluginEvent<ArgType> eventName, ArgType arg)
            where ArgType : PluginEventArgs
        {
            if (eventName == null)
            {
                return false;
            }

            eventName.Invoke(this, arg);

            return arg.Cancel;
        }

        /// <summary>
        /// 发出插件错误事件的公共函数
        /// </summary>
        private void EmitPluginErrorEvent(PluginErrorEventArgs arg)
        {
            if (OnError == null)
            {
                return;
            }

            OnError.Invoke(this, arg);
        }

        public void Dispose()
        {
            OnAssemblyLoading = null;
        }
    }
}
